package crmdna.interaction;

import static crmdna.common.AssertUtils.ensure;
import static crmdna.common.AssertUtils.ensureNotNull;
import static crmdna.common.OfyService.ofy;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

import com.googlecode.objectify.Key;
import com.googlecode.objectify.cmd.Query;

import crmdna.common.APIException;
import crmdna.common.APIResponse.Status;
import crmdna.common.DateUtils;
import crmdna.sequence.Sequence;
import crmdna.sequence.Sequence.SequenceType;

public class InteractionCore {

	private final static long MAX_CONTENT_SIZE = 10 * 1024;
	private final static long MAX_SUB_INTERACTIONS = 99;

	static InteractionProp createInteraction(String client, long memberId,
			String content, String interactionType, Date timestamp,
			long userId, String user) {

		ensureNotNull(timestamp, "timestamp is null");
		ensureNotNull(interactionType, "interactionType is null");
		ensure(interactionType.length() != 0, "interactionType is empty");
		ensureNotNull(user, "userEmail is null");
		ensure(user.length() != 0, "userEmail is empty");

		ensure(content.length() < MAX_CONTENT_SIZE,
				"A sub interaction content cannot have more than ["
						+ MAX_CONTENT_SIZE + "] characters");

		InteractionEntity interactionEntity = new InteractionEntity();
		interactionEntity.interactionId = Sequence.getNext(client,
				SequenceType.INTERACTION);
		long ns = DateUtils.getNanoSeconds(timestamp);
		interactionEntity.subInteractions.put(ns, content);
		interactionEntity.ms = timestamp.getTime();
		interactionEntity.memberId = memberId;
		interactionEntity.userId = userId;
		interactionEntity.user = user;
		interactionEntity.interactionType = interactionType;

		ofy(client).save().entity(interactionEntity).now();
		return interactionEntity.toProp();
	}

	static InteractionProp updateInteraction(String client, long interactionId,
			Long newMemberId, String newInteractionType, Long newUserId,
			String newUser) {

		ensureNotNull(client, "client is null");
		ensure(client.length() != 0, "client is empty");
		ensure(interactionId != 0, "interactionId is 0");

		InteractionEntity interactionEntity = safeGet(client, interactionId);

		if (newMemberId != null) {
			ensure(newMemberId != 0, "newMemberId is 0");
			interactionEntity.memberId = newMemberId;
		}

		if ((newInteractionType != null) && (newInteractionType.length() != 0))
			interactionEntity.interactionType = newInteractionType;

		if (newUserId != null) {
			ensure(newUserId != 0, "newUserId is 0");
			interactionEntity.userId = newUserId;
		}

		if (newUser != null) {
			ensure(newUser.length() != 0, "newUser is empty");
			interactionEntity.user = newUser;
		}

		ofy(client).save().entity(interactionEntity);

		return interactionEntity.toProp();
	}

	static void deleteInteraction(String client, long interactionId) {
		ensureNotNull(client, "client is null");
		ensure(client.length() != 0, "client is empty");
		ensure(interactionId != 0, "interaction id is 0");

		InteractionEntity interactionEntity = safeGet(client, interactionId);
		ofy(client).delete().entity(interactionEntity);
	}

	static void deleteSubInteraction(String client, long interactionId,
			long subInteractionId) {
		ensureNotNull(client, "client is null");
		ensure(client.length() != 0, "client is empty");
		ensure(interactionId != 0, "interaction id is 0");
		ensure(subInteractionId != 0, "subInteraction id is 0");

		InteractionEntity interactionEntity = safeGet(client, interactionId);

		if (!interactionEntity.subInteractions.containsKey(subInteractionId))
			throw new APIException().status(Status.ERROR_RESOURCE_NOT_FOUND)
					.message(
							"subInteractionId [" + subInteractionId
									+ "] not found in interaction ["
									+ interactionId + "]");

		interactionEntity.subInteractions.remove(subInteractionId);

		ofy(client).save().entity(interactionEntity);
	}

	static void createSubInteraction(String client, long interactionId,
			String content, Date timestamp) {

		ensureNotNull(client, "client is null");
		ensure(client.length() != 0, "client is empty");

		ensureNotNull(content, "content is null");
		ensure(content.length() < MAX_CONTENT_SIZE,
				"Sub interaction cannot have more than [" + MAX_CONTENT_SIZE
						+ "] characters");

		InteractionEntity interactionEntity = safeGet(client, interactionId);

		if (interactionEntity.subInteractions.size() >= MAX_SUB_INTERACTIONS)
			throw new APIException().status(Status.ERROR_OVERFLOW).message(
					"Cannot add more than [" + MAX_SUB_INTERACTIONS
							+ "] sub interactions for a single interaction");

		long ns = DateUtils.getNanoSeconds(timestamp);

		if (interactionEntity.subInteractions.containsKey(ns))
			throw new APIException().status(
					Status.ERROR_RESOURCE_ALREADY_EXISTS).message(
					"There is already a sub interaction for timestamp ["
							+ timestamp + "]");

		interactionEntity.subInteractions.put(ns, content);

		// update time stamp in interactionEntity if later
		interactionEntity.ms = Math.max(interactionEntity.ms,
				timestamp.getTime());

		ofy(client).save().entity(interactionEntity);
	}

	static void updateSubInteraction(String client, long interactionId,
			long subInteractionId, String content, Date timestamp) {

		ensureNotNull(client, "client is null");
		ensure(client.length() != 0, "client is empty");

		ensure(content.length() < MAX_CONTENT_SIZE,
				"Sub interaction cannot have more than [" + MAX_CONTENT_SIZE
						+ "] characters");

		InteractionEntity interactionEntity = safeGet(client, interactionId);

		if (!interactionEntity.subInteractions.containsKey(subInteractionId))
			throw new APIException().status(
					Status.ERROR_RESOURCE_ALREADY_EXISTS).message(
					"There is no sub interaction with id [" + subInteractionId
							+ "]");

		if (timestamp == null) {
			// update contents for the same sub interaction
			interactionEntity.subInteractions.put(subInteractionId, content);
		} else {
			interactionEntity.subInteractions.remove(subInteractionId);
			long ns = DateUtils.getNanoSeconds(timestamp);
			interactionEntity.subInteractions.put(ns, content);

			interactionEntity.ms = Math.max(interactionEntity.ms,
					timestamp.getTime());
		}

		ofy(client).save().entity(interactionEntity);
	}

	static InteractionQueryResult query(String client,
			InteractionQueryCondtion qc) {

		List<Key<InteractionEntity>> keys = queryKeys(client, qc);

		InteractionQueryResult qr = new InteractionQueryResult();
		qr.interactionQueryCondtion = qc;
		qr.totalSize = keys.size();

		if (qc.startIndex == null) {
			ensure(qc.numResults == null,
					"numResults should not be specified when startIndex is not specified");
			Map<Key<InteractionEntity>, InteractionEntity> map = ofy(client)
					.load().keys(keys);

			List<InteractionProp> props = new ArrayList<>(keys.size());
			for (Key<InteractionEntity> key : keys) {
				props.add(map.get(key).toProp());
			}

			qr.interactonProps = props;
			return qr;
		}

		// some keys need to be excluded
		ensure(qc.startIndex >= 0, "Invalid startIndex [" + qc.startIndex + "]");
		ensureNotNull(qc.numResults,
				"numResults should be specified when startIndex is specified");
		ensure(qc.numResults > 0, "Invalid numResults [" + qc.numResults + "]");

		if (qc.startIndex + 1 > keys.size()) {
			// no results to be fetched
			return qr;
		}

		ensure(keys.size() >= qc.startIndex);

		int numResults = Math.min(qc.numResults, keys.size() - qc.startIndex);
		ensure(numResults <= keys.size());
		ensure(numResults > 0);
		
		int endIndex = qc.startIndex + numResults;
		ensure(endIndex <= keys.size());
		ensure(endIndex > qc.startIndex);

		keys = keys.subList(qc.startIndex, endIndex);

		Map<Key<InteractionEntity>, InteractionEntity> map = ofy(client).load()
				.keys(keys);

		List<InteractionProp> props = new ArrayList<>(keys.size());
		for (Key<InteractionEntity> key : keys) {
			props.add(map.get(key).toProp());
		}

		qr.interactonProps = props;
		return qr;
	}

	static List<Key<InteractionEntity>> queryKeys(String client,
			InteractionQueryCondtion qc) {

		ensureNotNull(client, "client is null");
		ensure(client.length() != 0, "client is empty");

		ensureNotNull(qc, "InteractionQueryCondition is null");

		Query<InteractionEntity> q = ofy(client).load().type(
				InteractionEntity.class);

		if (qc.interactionTypes != null)
			if (!qc.interactionTypes.isEmpty())
				q = q.filter("interactionType in", qc.interactionTypes);

		if (qc.memberIds != null)
			if (!qc.memberIds.isEmpty())
				q = q.filter("memberId in", qc.memberIds);

		if (qc.userIds != null)
			if (!qc.userIds.isEmpty())
				q = q.filter("userId in", qc.userIds);

		if (qc.start != null) {
			q = q.filter("ms >=", qc.start.getTime());
		}

		if (qc.end != null) {
			q = q.filter("ms <=", qc.end.getTime());
		}

		List<Key<InteractionEntity>> keys = q.order("-ms").keys().list();

		return keys;
	}

	public static InteractionEntity safeGet(String client, long interactionId) {

		ensure(interactionId != 0, "interactionId is 0");
		InteractionEntity interactionEntity = ofy(client).load()
				.type(InteractionEntity.class).id(interactionId).get();

		if (null == interactionEntity)
			throw new APIException(Status.ERROR_RESOURCE_NOT_FOUND,
					"Interaction id [" + interactionId + "] does not exist");

		return interactionEntity;
	}
}
